<?php


namespace app\service;

use app\service\tool\FileService;
use app\service\tool\GenerateService;
use yii\httpclient\Client;

/**
 * 又拍云上传
 * Class UpYunService
 * @package app\service
 */
final class UpYunService extends BaseService
{

    /**
     * 实例
     * @var self
     */
    public static $instance;
    /**
     * 返回数据暂存
     * @var
     */
    private $responseData = [];
    /**
     * 又拍云的服务名
     * @var
     */
    private $bucket;
    /**
     * 授权的操作员
     * @var
     */
    private $operator;
    /**
     * 授权的操作员密码
     * @var string
     */
    private $password;
    /**
     * 访问域名
     * @var
     */
    private $domain;
    /**
     * api地址
     * @var
     */
    private $apiUrl;
    /**
     * 文件保存基础路径
     * @var string
     */
    public $basePath;
    /**
     * 保存路径 列表
     * @var array
     */
    private static $savePath = [
        'news' => '/www/news', // 新闻
        'block' => '/www/block', // 碎片
        'goods-thumb' => '/www/goods/thumb/' // 碎片
    ];

    function __construct()
    {

        parent::__construct();

        // 又拍云的服务名
        $this->bucket = 0;// \Yii::$app->params['upYun']['bucket'];
        // 授权的操作员
        $this->operator = 0;// \Yii::$app->params['upYun']['operator'];
        // 授权的操作员密码
        $this->password = 0;// md5(\Yii::$app->params['upYun']['password']);
        // 访问域名
        $this->domain = 0;// \Yii::$app->params['upYun']['domain'];
        // api地址
        $this->apiUrl = 'https://v0.api.upyun.com';
    }

    public function rules()
    {
        return [
            ['basePath', 'string']
        ];
    }

    public function attributeLabels()
    {
        return [
            'basePath' => '基础路径'
        ];
    }

    /**
     * 加载整体[Model]
     * @param array $data 初始化的数据
     * @return self
     */
    public static function loadModel($data = [])
    {

        return self::getInstance()->initInstance($data);
    }

    /**
     * 获取实例
     * @return self
     */
    protected static function getInstance()
    {

        // 实例不存在初始化
        if (empty(self::$instance)) self::$instance = new self();

        // 存在直接返回
        return self::$instance;
    }

    /**
     * 初始化一些默认信息
     * @param array $_data
     * @return self
     */
    protected function initInstance($_data = [])
    {

        // 获取实例
        $instance = self::getInstance();

        // ******** 做一些参数的赋值 ********
        foreach ($_data as $k => $v) {

            if (empty($_data) || !$this->hasAttribute($k)) {
                unset($_data[$k]);
            }
        }

        $instance->setAttributes($_data);

        // 返回
        return $instance;
    }

    /**
     * 外部调用删除图片
     * @param $paths
     * @return bool
     */
    public function deleteImage($paths)
    {
        if (empty($paths)) {
            return false;
        }

        // 数组走循环
        if (is_array($paths)) {
            $this->responseData = [];
            foreach ($paths as $path) {
                if (!$this->doDelete($path)) return false;
            }
            return true;
        }

        $this->responseData = '';
        // 非数组走普通
        return $this->doDelete($paths);
    }

    /**
     * 文件上传
     * @param $file
     * @param string $savePath
     * @return bool
     */
    public function uploadImage($file, $savePath = null)
    {

        // 方法内自定义了路径赋值
        if (!empty($savePath)) {
            $this->basePath = $savePath;
        }
        // 验证路径是否为空
        if (empty($this->basePath)) {
            $this->addError('save-path', '保存路径不能为空');
        }
        // 有错误返回 失败
        if ($this->hasErrors()) return false;
        // 最终结果是 字符串走单个
        $this->responseData = '';
        $GMTdate = gmdate('D, d M Y H:i:s \G\M\T');

        // 加载[PolicyPolicy]
        $options = array();
        $options['bucket'] = $this->bucket;
        $options['expiration'] = time() + 3600;
        // 保存路径
        $options['save-key'] = $this->renderPath($file);
        $options['date'] = $GMTdate;
        $policy = $this->getPolicy($options); // policy 生成
        // 鉴权
        $authorization = $this->getAuthorization('POST', '/' . $options['bucket'],
            $GMTdate, $policy);

        try {

            // 请求初始化
            $client = (new Client())->createRequest();
            // 添加参数
            $client->setMethod('POST')
                ->setUrl($this->apiUrl . '/' . $options['bucket'])
                ->setData([
                    'authorization' => $authorization,
                    'policy' => $policy
                ])
                ->addFile('file', $file);
            // 发送请求
            $response = $client->send();

            // 请求结果解析
            $result = $this->resolveResponse($response->data, $response->getStatusCode());
            // 解析错误返回否
            if (!$result) return false;

            return true;
        } catch (\Exception $exception) {

            // 记录下错误日志
            \Yii::error([
                "************************* 请求发送类错误 *************************",
                "错误详情：[又拍云]请求发送失败",
                $exception->getMessage(),
                "错误详情: ",
                $exception->getTraceAsString()
            ], 'error');

            $this->addError('500', '请求发送失败：' . $exception->getMessage());
            return false;
        }
    }

    /**
     * 生成[policy]
     * @param array $params
     * @return string
     */
    public function getPolicy($params = [])
    {
        return base64_encode(
            json_encode($params, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
        );
    }

    /**
     * 获取[认证鉴权]
     * @param $method
     * @param $uri
     * @param $date
     * @param null $policy
     * @param null $md5
     * @return string
     */
    private function getAuthorization($method, $uri, $date, $policy = null,
                                      $md5 = null)
    {

        $endOpts = array();
        foreach (array($method, $uri, $date, $policy, $md5) as $v) {
            if ($v) $endOpts[] = $v;
        }
        $value = implode('&', $endOpts);
        $sign = base64_encode(hash_hmac('sha1', $value, $this->password, true));
        return 'UPYUN ' . $this->operator . ':' . $sign;
    }

    /**
     * 渲染文件最终存储路径
     * @param string $filePath 文件名称、路径
     * @return string
     */
    private function renderPath($filePath)
    {
        $pathinfo = pathinfo($filePath);
        $random = GenerateService::getRandomStr(8);
        $nowTime = time();
        return '/' . $this->basePath .
            "/{year}-{mon}-{day}/file_T_" . $nowTime .
            "_T_{$random}.{$pathinfo["extension"]}";

    }

    /**
     * 删除某图片
     * @param $path
     * @param bool $async
     * @return bool
     */
    private function doDelete($path, $async = true)
    {

        $bucket = $this->bucket; // 又拍云的服务名
        $method = 'DELETE';
        $GMTdate = gmdate('D, d M Y H:i:s \G\M\T');
        $deleteDir = '/' . ltrim($path, '/');
        $uri = "/" . $bucket . $deleteDir;
        $policy = null;
        $str = $method . '&' . $uri . '&' . $GMTdate;
        $authorization = $this->getAuthorization($str);

        try {
            $client = new Curl();
            $response = $client
                ->setHeaders([
                    'authorization' => $authorization,
                    'Date' => $GMTdate,
                    'x-upyun-async' => $async
                ])
                ->delete($this->apiUrl . '/' . $bucket . $deleteDir);

            // 请求结果解析
            $result = $this->resolveResponse($response, $client->responseCode);
            // 解析错误返回否
            if (!$result) return false;

            return true;
        } catch (\Exception $exception) {

            // 记录下错误日志
            \Yii::error([
                "************************* 请求发送类错误 *************************",
                "错误详情：[又拍云]请求发送失败",
                $exception->getMessage(),
                "错误详情: ",
                $exception->getTraceAsString()
            ], 'error');

            $this->addError('500', '请求发送失败：' . $exception->getMessage());
            return false;
        }

    }

    /**
     * 解析返回数据
     * @param $response
     * @param $code
     * @return bool
     */
    private function resolveResponse($response, $code)
    {

        // 返回 false 就是有错误
        switch ($code) {

            case 'timeout':
                $this->addError('timeout', '请求链接未响应');
                break;

            case 500:
                //500 logic here
                $this->addError('500', '服务器错误网页暂时无法访问');
                break;

            case 404:
                //404 Error logic here
                $this->addError('404', '页面未找到');
                break;

            case 302:
                //404 Error logic here
                $this->addError('302', '页面重定向');
                break;
        }

        // 已有错误返回
        if ($this->hasErrors()) return false;

        // 文本转数组
        if (is_string($response)) $response = json_decode($response, true);

        // * 在此处添加自定义错误解析等代码
        if ($code != 200) {
            $this->addError($response['code'], $response['message']);
            return false;
        }

        if (!empty($response->data) && !empty($response->data['url'])) {
            $response['url'] = $this->domain . $response->data['url'];
        }

        // 最终返回数据暂存
        if (is_array($this->responseData)) $this->responseData[] = $response;
        else $this->responseData = $response;

        return true;
    }

    /**
     * 获取错误信息
     */
    public static function getErrorChinese()
    {
        /// $list =
    }

    /**
     * 获取最终数据
     * @param string $key | 必须传输字符串
     * @return array|mixed|null
     */
    public function getResult($key = '')
    {

        // 结果非数组返回下
        if (!empty($this->responseData[$key])) {

            // 如果有 地址加又拍云地址
            if (!empty($this->responseData['url']) &&
                !strstr($this->responseData['url'], $this->domain))
                $this->responseData['url'] = FileService::addImgDomain($this->responseData['url'],
                    null, '');

            return $this->responseData[$key];
        }

        $end = [];
        foreach ($this->responseData as $k => $v) {

            if ($key == 'url' && !empty($v[$key]) && !strstr($v[$key], $this->domain))
                $v[$key] = FileService::addImgDomain($v[$key], null, '');;

            if (!empty($v[$key])) {
                $end[$k] = $v[$key];
            } else {
                $end[$k] = $v;
            }
        }

        return $end;
    }
}
